﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using Shell.PocoDot.Configurations;
using System.Linq;

namespace Shell.PocoDot
{
    class EfDataContext<TPocoDot> : IDataContext<TPocoDot>
        where TPocoDot : PocoDotBase
    {
        private bool disposed;

        public EfDataContext(ProviderConfiguration config)
        {
            this.Configuration = config;
            this.Initialize();
        }

        protected virtual DbSet<TPocoDot> Data
        {
            get;
            private set;
        }

        protected virtual DbContext DataContext
        {
            get;
            private set;
        }

        protected virtual ProviderConfiguration Configuration
        {
            get;
            private set;
        }

        public int Submit()
        {
            return this.DataContext.SaveChanges();
        }

        public TPocoDot Create()
        {
            var dot = this.Data.Create();
            dot.Id = Guid.NewGuid();
            this.Data.Add(dot);
            return dot;
        }

        public TPocoDot Add(TPocoDot dot)
        {
            dot.Id = Guid.NewGuid();
            this.Data.Add(dot);
            return dot;
        }

        public TPocoDot Update(TPocoDot dot)
        {
            var original = this.Read(dot.Id);
            if (original != null)
            {
                this.UpdateValue(dot, original);
                return original;
            }
            throw new InvalidOperationException(SR.Get("Error.PocoDotNotExist", typeof(TPocoDot).Name, dot.Id));
        }

        public IQueryable<TPocoDot> Read()
        {
            return this.Data;
        }

        public IQueryable<TPocoDot> Read(IEnumerable<Guid> keys)
        {
            if (keys == null)
            {
                throw new ArgumentNullException("keys");
            }
            return this.Data.Where(d => keys.Contains(d.Id));
        }

        public TPocoDot Read(Guid key)
        {
            return this.Data.SingleOrDefault(d => d.Id == key);
        }

        public void Delete(Guid key)
        {
            var original = this.Read(key);
            if (original == null)
            {
                throw new InvalidOperationException(SR.Get("Error.PocoDotNotExist", typeof(TPocoDot).Name, key));
            }
            this.Data.Remove(original);
        }

        public void Rollback()
        {
            this.Initialize();
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Initialize()
        {
            if (this.DataContext != null)
            {
                this.DataContext.Dispose();
            }
            var cfgName = this.Configuration.ConnectionStringOrName;
            var dbmodel = this.Configuration.Compile();
            this.DataContext = new DbContext(cfgName, dbmodel);
            if (this.Configuration.CreateIfNotExists)
            {
                this.DataContext.Database.CreateIfNotExists();
            }
            this.Data = this.DataContext.Set<TPocoDot>();
        }

        protected virtual void UpdateValue(TPocoDot dot, TPocoDot original)
        {
            var type = typeof(TPocoDot);
            foreach (var p in type.GetProperties())
            {
                var newValue = p.GetValue(dot, null);
                p.SetValue(original, newValue, null);
            }
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    if (this.DataContext != null)
                    {
                        this.DataContext.Dispose();
                        this.DataContext = null;
                    }
                    this.Data = null;
                    this.Configuration = null;
                }
            }
            this.disposed = true;
        }
    }
}
